<?php
// +-------------------------------------------------------------------
// | 
// +-------------------------------------------------------------------
// | Copyright (c) 2009-2016 All rights reserved.
// +-------------------------------------------------------------------
namespace Kcdns\Admin\Model;

class CommonModel extends \Think\Model
{

    protected $name;

    protected $tableName;

    public $cfg;

    public function __construct($name = '', $tablePrefix = '', $connection = '')
    {
        parent::__construct($name == 'Common' ? 'config' : $name, $tablePrefix, $connection);
        // 修正 name, 替换为表名
        $this->trueTableName = $this->getTableName();
        $this->name = strpos($this->trueTableName, $this->tablePrefix) === 0 ? substr($this->trueTableName, strlen($this->tablePrefix)) : $this->trueTableName;
    }

    public function setTable($table)
    {
        $this->name = $table;
        $this->tableName = $table;
        $this->db = null;
        $this->fields = null;
        $this->trueTableName = null;
        parent::__construct($table, $this->tablePrefix);
        return $this;
    }

    // *********************************************************************************************
    // ******************************************** 列表 ********************************************
    // *********************************************************************************************
    public function getList($where = [], $list = [])
    {
        unset($where['_pjax']);
        unset($where['_']);

        $deleteWhere = [];
        foreach ($this->getDbFields() as $v) {
            if ($v == 'is_delete') {
                if (!empty($this->tableName)) {
                    $deleteWhere[$this->tableName . '.is_delete'] = 0;
                } else {
                    $deleteWhere['is_delete'] = 0;
                }
                break;
            }
        }

        if ($list) {
            list ($count, $data) = $list;
        } else {
            $request = $this->cfg->getRequest();
            $listOptions = $this->cfg->getListModelOptions($request);
            $listOptions['request_where'] = $request['where'];
            $listOptions['list_where'] = $where;
            $listOptions['where'] = array_merge($request['where'], $deleteWhere, $where ?: []);
            $listOptions['order'] = $request['order'];
            $listOptions['limit'] = $request['limit'];

            list ($count, $data) = $this->_getList($listOptions);
        }


        $this->_formatListButton();

        foreach ($data as &$row) {
            // 绑定操作按钮
            $row['_opration_'] = $this->_formatListOpration($row);

            foreach ($row as $k => &$v) {
                // 数据格式化, 翻译, 修正格式等
                $k == '_opration_' or $v = $this->_formatListField($k, $v, $row);
            }
        }

        return compact('data', 'count');
    }

    /**
     * 查询列表
     * 在子类中覆写此方法, 实现对列表的定制查询需求
     *
     * @param unknown $listOptions
     */
    protected function _getList($listOptions)
    {
        list($field_arr, $field_tpl) = $this->fieldFormat($listOptions);

        $count = $this->table(C('DB_PREFIX') . $this->name)->alias("`{$this->name}`")->join($listOptions['join'])->where($listOptions['where'])->count() ?: 0;
        $data = $this->table(C('DB_PREFIX') . $this->name)->alias("`{$this->name}`")->field($field_arr)->join($listOptions['join'])->where($listOptions['where'])->order($listOptions['order'])->limit($listOptions['limit'])->select() ?: [];

        //虚拟字段数据填充
        $new_data = [];
        foreach ($data as $key => $row) {
            $row = $this->formatCallback($row);
            $new_data[] = array_merge($field_tpl, $row);
        }

        return [
            $count,
            $new_data
        ];
    }

    public function fieldFormat($listOptions)
    {
        static $table_fields = [];
        $field_arr = [];
        $field_tpl = [];

        //表字段过滤才能支持虚拟字段
        $old_field_arr = array_map('trim', explode(',', trim(str_replace('`', '', $listOptions['field']))));
        foreach ($old_field_arr as $val) {
            $arr2 = array_map('trim', explode(' as ', trim($val)));
            $arr3 = array_map('trim', explode('.', trim($arr2[0])));
            list($_table, $_field) = $arr3;

            $_table_=preg_replace('/(.+?)([\d]?)$/','$1',$_table);
            if (!$table_fields[$_table]) {
                $table_fields[$_table] = M($_table_)->getDbFields();
            }
            if (in_array($_field, $table_fields[$_table])) {
                $field_arr[] = "`{$_table}`.`{$_field}` as `{$_table}.{$_field}`";
            }

            $field_tpl["{$_table}.{$_field}"] = '-';
        }

        return [
            $field_arr,
            $field_tpl
        ];
    }

    /**
     * 格式化字段
     *
     * @param 字段名 $k
     * @param 数据库值 $v
     * @param 数据库行 $row
     */
    protected function _formatListField($k, $v, $row)
    {
        $field = $this->cfg->listGrid[$k];
        $table = $this->name;

        // 使用字典翻译
        if ($field['value_dic']) {
            $v = isset($field['value_dic'][$v]) ? $field['value_dic'][$v] : null;
        } // 使用回调函数修正
        elseif ($field['value_callback']) {
            unset($row['_opration_']);
            $paramData = $row;
            $paramData['row'] = $row;
            $cfg = $this->cfg;

            // 将参数中的字段[member.uid]或行[row]替换为当前数据
            // 自动补全字段表前缀 : [member.uid] 可简写为 [uid]
            foreach ($field['value_callback_param'] as $_pk => $_pv) {
                $field['value_callback_param'][$_pk] = unserialize(preg_replace_callback('/\[([a-zA-Z_\.]+)\]/', function ($match) use ($paramData, $table) {
                    $field = $match[1];
                    if (!strpos($match[1], '.') && $field !== 'row') {
                        $field = "{$table}.{$field}";
                    }
                    return serialize($paramData[$field]);
                }, $_pv));
            }
            $v = call_user_func_array($field['value_callback'], $field['value_callback_param']);
        }

        return $v === '' || $v === null ? '-' : $v;
    }

    /**
     * 格式化行操作按钮
     *
     * @param 数据库行 $row
     */
    protected function _formatListOpration($row)
    {
        $opration = $this->cfg->listOpration;
        foreach ($opration as $k => &$v) {
            if (!checkRule($v['url'])) {
                unset($opration[$k]);
            }
            // 可以在子类中根据 $v['key'] 动态修改按钮属性
            // 替换地址中的字段 : id=[action_log.id] -> id=1024
            $v['url'] = $this->_bindUrl($v['url'], $row);
        }
        return $opration;
    }

    /**
     * 格式化列表操作按钮
     *
     * @param 数据库行 $row
     */
    protected function _formatListButton()
    {
        foreach ($this->cfg->listBtutton as $k => $btn) {
            if (!checkRule($btn['url'])) {
                continue;
            }

            $btn['url'] = preg_replace_callback('/([{\[])([a-zA-Z_\.]+)([}\]])/', function ($match) use ($table) {
                $type = $match[1] == '[' ? 'field_and_data' : 'field_only';
                $field = $match[2];
                return $this->cfg->escape($field) . ($type == 'field_and_data' ? '=' . $_GET[$field] : '');
            }, $btn['url']);

            $this->cfg->listBtutton[$k] = $btn;
        }
    }

    /**
     * 绑定数据, 生成 url
     *
     * @param 地址模板 $urlTpl
     * @param 数据库行 $row
     */
    protected function _bindUrl($urlTpl, $row)
    {
        $table = $this->name;

        //新的匹配规则
        $_arr = explode('?', $urlTpl);
        list($_url, $_param_str) = $_arr;
        $_arr2=explode('&',$_param_str);
        $_args = [];
        foreach ($_arr2 as $_v) {
            if(preg_match('/^\[.+\]$/',$_v)){
                $field=trim($_v,'[]');
                $_args[$this->cfg->escape($field)]=$row[$field];
            }else{
                list($field,$value)=explode('=',$_v);
                $_args[$field]=$value;
            }
        }
        return U($_url, $_args);

        // 绑定数据 : [table.field]
        // 绑定字段名 : {table.field}
        $url = preg_replace_callback('/([{\[])([a-zA-Z_\.]+)([}\]])/', function ($match) use ($row, $table) {
            $type = $match[1] == '[' ? 'field_and_data' : 'field_only';
            $field = $match[2];
            if (!strpos($field, '.')) {
                $field = "{$table}.{$field}";
            }
            return $this->cfg->escape($field) . ($type == 'field_and_data' ? '=' . $row[$field] : '');
        }, $urlTpl);

        return U($url);
    }

    // *********************************************************************************************
    // ******************************************** 新增 ********************************************
    // *********************************************************************************************
    /**
     * 只支持单表插入
     */
    public function insert($data = [], $fillData = [])
    {
        $formData = $this->_getFormData($data, $fillData);
        $result = $this->_insert($formData);
        return $result;
    }

    public function formatCallback($row)
    {
        static $set_listGrid = false;
        $class = ucfirst($this->cfg->name);
        $formatCallback = '\\Service\\' . $class . '\\Service::' . __FUNCTION__;
        if (is_callable($formatCallback)) {
            $result = call_user_func_array($formatCallback, [$row]);
            $row = array_merge($row, (array)$result);

            if (!$set_listGrid) {
                $set_listGrid = true;
                if($result){
                    foreach ($result as $key => $val) {
                        //禁止排序搜索
                        $this->cfg->listGrid[$key]['sort'] = 0;
                        $this->cfg->listGrid[$key]['search'] = 0;
                    }
                }

            }
        }

        return $row;
    }

    /**
     * 格式化表单提交数据
     * 1.将一维数组 [table.field=>value] 转换为二维数组 [[table][field]=>value]
     * 2.过滤掉非法提交参数
     * 3.使用 filter 函数进行格式校验
     *
     * 可以传入填充数据
     *
     * @param array $data
     */
    public function _getFormData($data = [], $fillData = [])
    {
        $dataFilter = [];
        foreach ($this->cfg->formField as $_setting) {
            // 剔除只读字段
            if ($_setting['readonly']) {
                continue;
            }
            $dataFilter[$_setting['name']] = $_setting['rule'];

            // 修正图片字段
            $_setting['type'] == 'picture' and $data[$_setting['name']] = $this->_imageToId($data[$_setting['name']]);
            $_setting['type'] == 'file' and $data[$_setting['name']] = $this->_fileToId($data[$_setting['name']]);
        }
        filter($data, $dataFilter);

        $formatData = [];

        $data = array_merge($data, $fillData);
        foreach ($data as $_field => $_value) {
            list ($table, $field) = explode('.', $_field);
            $formatData[$table][$field] = $_value;
        }

        return $formatData;
    }

    /**
     * 在子类中覆写此方法, 实现多表插入等定制需求
     */
    protected function _insert($formData)
    {
        // 自动补全 创建/更新时间
        $timeFillData = [
            'create_time' => date('Y-m-d H:i:s'),
        ];
        $insertData = array_merge($formData[$this->name], $timeFillData, $fillData ?: []);
        return $this->data($insertData)->add();
    }

    // *********************************************************************************************
    // ******************************************** 编辑 ********************************************
    // *********************************************************************************************
    /**
     * 只支持单表更新
     */
    public function update($data = [], $fillData = [], $where = [])
    {
        $formData = $this->_getFormData($data, $fillData);

        $pkWhere = [
            $this->getPk() => $formData[$this->name][$this->getPk()]
        ];
        $where = $this->_mainWhere($pkWhere, $where);
        return $this->_update($formData, $where);
    }

    // 执行更新/删除操作时, 只能操作主表, 合并查询条件, 只保留主表字段
    protected function _mainWhere($where, $mergeWhere = [])
    {
        $where = array_merge($where ?: [], $mergeWhere ?: []);
        foreach ($where as $k => $v) {
            if (strpos($k, '.')) {
                list ($_table, $_field) = explode('.', $k);
                if ($_table == $this->name) {
                    $where[$_field] = $v;
                    unset($where[$k]);
                }
            }
        }
        return $where;
    }

    /**
     * 在子类中覆写此方法, 实现多表更新等定制需求
     */
    protected function _update($formData = [], $where = [])
    {
        // 自动补全更新时间
        $timeFillData = in_array('update_time', $this->getDbFields()) ? [
            'update_time' => date('Y-m-d H:i:s')
        ] : [];
        $updateData = array_merge($formData[$this->name], $timeFillData);
        $result = $this->data($updateData)->where($where)->save();
        $return = $result !== false;
        return $return;
    }

    // *********************************************************************************************
    // ******************************************** 查询 ********************************************
    // *********************************************************************************************
    /**
     * 获取单条记录
     * 使用连表条件
     *
     * @param mixed $idOrRow
     *            主键值或包含主键值的数组
     */
    public function getData($idOrRow, $where = [])
    {
        //表单模型无数据支持
        if (!$idOrRow && !$where) {
            return [];
        }

        $table = $this->name;
        $formOptions = $this->_getDataWhere($idOrRow, $where);
        $data = $this->_getData($formOptions);

        if (!$data) {
            throw new \Exception('查询无效');
        }

        foreach ($this->cfg->formField as $_field => $_setting) {
            // 修正图片字段
            $_setting['type'] == 'picture' and $data[$_setting['name']] = $this->_idToImage($data[$_setting['name']]);
            $_setting['type'] == 'file' and $data[$_setting['name']] = $this->_idToFile($data[$_setting['name']]);

            // 使用回调函数修正
            if ($_setting['value_callback']) {
                $paramData = $data;
                $paramData['row'] = $data;

                // 将参数中的字段[member.uid]或行[row]替换为当前数据
                // 自动补全字段表前缀 : [member.uid] 可简写为 [uid]
                foreach ($_setting['value_callback_param'] as $_pk => $_pv) {
                    $_setting['value_callback_param'][$_pk] = unserialize(preg_replace_callback('/\[([a-zA-Z_\.]+)\]/', function ($match) use ($paramData, $table) {
                        $_setting = $match[1];
                        if (!strpos($match[1], '.') && $_setting !== 'row') {
                            $_setting = "{$table}.{$_setting}";
                        }
                        return serialize($paramData[$_setting]);
                    }, $_pv));
                }
                $data[$_field] = call_user_func_array($_setting['value_callback'], $_setting['value_callback_param']);
            }
        }

        return $data;
    }

    /**
     * 单条查询条件
     *
     * @param mixed $idOrRow
     * @param array $where
     */
    protected function _getDataWhere($idOrRow, $where = [])
    {
        $pk = $this->name . '.' . $this->getPk();

        $request = is_array($idOrRow) ? [
            $pk => $idOrRow[$pk]
        ] : [
            $pk => $idOrRow
        ];

        $formOptions = $this->cfg->getFormModelOptions();
        $formOptions['where'] = array_merge($request, $where ?: []);
        return $formOptions;
    }

    /**
     * 在子类中覆写此方法, 实现定制查询
     *
     * @param unknown $formOptions
     */
    protected function _getData($formOptions)
    {
        return $this->table(C('DB_PREFIX') . $this->name)->alias("`{$this->name}`")->field($formOptions['field'])->join($formOptions['join'])->where($formOptions['where'])->find();
    }

    // *********************************************************************************************
    // ******************************************** 更新 ********************************************
    // *********************************************************************************************
    /**
     * 批量更新字段
     * 用于更新状态等
     * 只支持单表更新
     */
    public function set($ids, $data = [], $where = [])
    {
        $setOptions = $this->_setOptions($ids, $data, $where);
        return $this->_set($setOptions);
    }

    // 计算更新选项
    protected function _setOptions($ids, $data = [], $where = [])
    {
        $pk = $this->name . '.' . $this->getPk();

        // 参数中含有主键, 更新单条记录
        if (isset($data[$pk])) {
            $ids = $data[$pk];
            unset($data[$pk]);
        }

        $sData = [];
        foreach ($data as $k => $v) {
            list ($table, $field) = explode('.', $k);
            $sData[$table][$field] = $v;
        }

        // 批量更新
        if ($ids) {
            $request = [
                $this->getPk() => [
                    'IN',
                    is_array($ids) ? $ids : [
                        $ids
                    ]
                ]
            ];
        }

        $setOptions = [
            'data' => $sData,
            'where' => $this->_mainWhere($request, $where)
        ];
        return $setOptions;
    }

    /**
     * 在子类中覆写此方法, 实现多表更新等定制需求
     */
    protected function _set($setOptions)
    {
        // 自动补全更新时间
        $timeFillData = [
            'update_time' => date('Y-m-d H:i:s')
        ];
        $updateData = array_merge($setOptions['data'][$this->name], $timeFillData);
        return $this->data($updateData)->where($setOptions['where'])->save() !== false;
    }

    // *********************************************************************************************
    // ******************************************** 删除 ********************************************
    // *********************************************************************************************
    /**
     * 批量删除
     * 只支持单表删除
     *
     * @param unknown $ids
     *            主键列表
     * @param unknown $where
     */
    public function del($ids, $where)
    {
        $request = [
            $this->getPk() => [
                'IN',
                is_array($ids) ? $ids : [
                    $ids
                ]
            ]
        ];

        $delOptions['where'] = $this->_mainWhere($request, $where);
        if (empty($delOptions['where'])) {
            throw new \Exception('删除条件错误');
        }

        $delOptions['delete_field'] = false;
        foreach ($this->getDbFields() as $v) {
            if ($v == 'is_delete') {
                $delOptions['delete_field'] = $v;
                break;
            }
        }

        return $this->_del($delOptions);
    }

    /**
     * 在子类中覆写此方法, 实现多表删除等定制需求
     *
     * @param unknown $delOptions
     */
    protected function _del($delOptions)
    {
        if ($delOptions['delete_field']) {
            return $this->data([
                    $delOptions['delete_field'] => 1,
                    'update_time' => date('Y-m-d H:i:s')
                ])->where($delOptions['where'])->save() !== false;
        }

        return $this->where($delOptions['where'])->delete();
    }

    // *********************************************************************************************
    // ******************************************** 图片/文件字段修正 *******************************
    // *********************************************************************************************
    protected function _idToImage($value)
    {
        $images = explode(',', $value);
        foreach ($images as $k => $v) {
            if (is_numeric($v)) {
                $images[$k] = M('picture')->where([
                    'id' => $v
                ])->getField('url');
            }
            if (!$images[$k]) {
                unset($images[$k]);
            }
        }
        return implode(',', $images);
    }

    protected function _imageToId($value)
    {
        $ids = explode(',', $value) ?: [];
        foreach ($ids as $k => $v) {
            if (!is_numeric($v)) {
                $ids[$k] = M('picture')->where([
                    'url' => $v
                ])->getField('id');
                if (!$ids[$k]) {
                    unset($ids[$k]);
                }
            }
        }
        return implode(',', $ids);
    }

    protected function _idToFile($value)
    {
        $ids = explode(',', $value);
        $files = [];
        foreach ($ids as $id) {
            $file = is_numeric($id) ? M('picture')->where([
                'id' => $id
            ])->find() : [];

            return $file ?: '';

            $file and $files[] = $file;
        }
        return json_encode($files);
    }

    protected function _fileToId($value)
    {
        $files = json_decode($value, true) ?: [];
        $ids = [];
        foreach ($files as $file) {
            isset($file['id']) and is_numeric($file['id']) and $ids[] = $file['id'];
        }
        return implode(',', $ids);
    }
}
